1. Introduction
Ce cours porte sur la Conception et Programmation Objet Avancée.
1.1. Concepts, bonnes pratiques et patrons
Vous avez appris (cf. M2103 et M2104) un certain nombre de concepts objets :
-
Abstraction
-
Encapsulation
-
Polymorphisme
-
Héritage
Vous avez/allez ensuite appris des bonnes pratiques :
-
Identifier les aspects qui varient et les séparer des aspects constants
-
Programmer une interface, non une implémentation
-
Préférer la composition à l’héritage
-
Les classes doivent être ouvertes à l’extension, mais fermées à la modification
-
Dépendez d’abstractions. Ne dépendez pas de classes concrètes (inversion des dépendances)
-
Ne parlez pas aux inconnus
L’étape suivante consiste à apprendre les bonnes solutions de conception, ce qu’on appelle les patrons de conception (ou design patterns en anglais).
1.2. Organisation
|
Rappel du rythme : 1 cours, 1 TD et 2 TPs par semaine. Pendant 8 semaines. |
-
La première semaine est consacré au principe des patrons de conception, en partant d’un exemple (cours en fin de semaine).
-
Les 5 ou 6 suivantes sont consacrées à l’étude de certains patrons classiques. Mise en pratique sur des exercices en TP.
Les TPs sont décalés d’une semaine (conception et étude d’un ou plusieurs patrons semaine
Net mise en oeuvre en TP semaineN+1). -
Les 2 ou 3 suivantes, les étudiants en mode projet pour faire du refactoring d’applications réelles (conception aidée en TD sur les modèles UML™, mise en oeuvre en TP).
Voici une proposition de déroulement des semaines :
- Semaine 1
-
SuperCanard, le grand classique, [Stratégie]
- Semaine 2
- Semaine 3
-
Patrons [Fabrique], [Proxy], [Etat]
- Semaine 4
-
-
-
version intuitive (2 interfaces)
-
version Java (classe
Observable)
-
-
- Semaine 5
-
L’exemple de Meyer : menus en objet
- Semaine 6
-
-
Patrons Décorateur, Façade, Visiteur
-
MVC avec l’exemple
JTablede Java -
Patrons Chaîne de responsabilité (juste en cours)
-
- Semaines 7 et 8
-
-
Quelques idées de projet final :
-
Refactorer un code généré par Umple.
-
Refactorer le code de MPA (mais pas le leur, celui d’un autre groupe)
-
-
1.3. Evaluation et notation
Comme prévu par le planning des contrôles, les étudiants auront :
-
une note de projet (TPs + projet final) ⇒ coef. 1
-
une note d’examen final (semaine 5) ⇒ coef. 2
2. Rappels sur des éléments Java importants
2.1. Importance du typage
2.1.1. Différents types de typage
Le fait d’attribuer un type (une classe) à une variable (un objet) peut se faire de plusieurs façons :
-
statique
-
dynamique
-
duck typing
2.1.2. Typage statique
On parle de typage statique quand la majorité des vérifications de type sont effectuées au moment de la compilation.
int i = 0; // cette déclaration indique explicitement que
// la variable i est de type entier
2.1.3. Typage dynamique
Le typage dynamique consiste à laisser l’ordinateur réaliser l’opération de typage à la volée, lors de l’exécution du code.
/**
* @author André Peninou
*/
public class Type {
void m() {
System.out.println ("Type");
}
}
public class SousType extends Type {
void m() {
System.out.println ("SousType");
}
void autreM(){
System.out.println ("Spécifique SousType");
}
}
...
Type a = new Type();
a.m(); // "Type"
a = new SousType();
a.m(); // "SousType"
// Statique : a est un Type (à la compil)
// Dynamique : a est un SousType au runtime.
// D'où :
a = new SousType();
a.autreM();
// NOK car type statique == A => autreM() n'existe pas à la compilation
...
2.1.4. Duck typing
Style de typage dynamique où la sémantique d’un objet (c’est-à-dire son type) est déterminée par l’ensemble de ses méthodes et de ses attributs, et non par un type défini et nommé explicitement par le programmeur.
|
L’origine de cette expression est liée à cette citation :
— James Whitcomb Riley
|
def calcule(a, b, c)
return a*b+c
end
$a = calcule(6, 3, 2)
$b = calcule('6', 3, ', the number of the beast')
puts $a.to_s
puts $b.to_s
Ce qui donne :
20 666, the number of the beast
| Pour aller plus loin : http://fr.wikipedia.org/wiki/Duck_typing |
2.2. Importance de la visibilité
Dès que l’on commence à avoir une application conséquente, l’organisation en package devient obligatoire. Revenons donc sur les questions de visibilité des propriétés et méthodes, qui seront importants dans la plupart des aspects de ce module.
Si un champ d’une classe A :
-
est private, il est accessible uniquement depuis sa propre classe ;
-
a la visibilité package (visibilité par défaut, pas de mot-clef), il est accessible de partout dans le paquetage de
Amais de nulle part ailleurs ; -
est protected, il est accessible de partout dans le paquetage de
Aet, siAest publique, grosso modo dans les classes héritant deAdans d’autres paquetages ; -
est public, il est accessible de partout dans le paquetage de
Aet, siAest publique, de partout ailleurs.
| Ci-dessus, les niveaux de visibilité sont rangés par visibilité croissante. |
package UN;
public class A {
protected String attrprotected;
String attrfriend; // friend
}
Si on définit une deuxième classe dans le même package :
package UN;
class B {
...
{
A a = new A ();
a.attrprotected// OK : même si bizarre
a.attrfriend // OK : visible package
}
}
package UN;
class C extends A {
...
{
this.attrprotected// OK : normal
this.attrfriend // OK : visible package
}
}
package DEUX;
class B {
...
{
A a = new A ();
a.attrprotected// NON OK : normal
a.attrfriend // NON OK : normal, proche de "private"
}
}
class C extends A {
...
{
this.attrprotected// OK : normal car protected et héritage
this.attrfriend // NON OK : normal, proche de "private"
}
}
À la question private ou protected ? Quel est le mieux pour les attributs ?
-
C’est une question de style de programmation !
-
Puristes (Meyer) ⇒
private -
Parfois utile : cf. Strategy, évite les getters/setters
| Il n’y a pas de visibilité par défaut en UML™. |
2.3. Retour sur les Membres static
class VariableDemo
{
static int count=0;
public void increment()
{
count++;
}
public static void main(String args[])
{
VariableDemo obj1=new VariableDemo();
VariableDemo obj2=new VariableDemo();
obj1.increment();
obj2.increment();
System.out.println("Obj1: count is="+obj1.count);
System.out.println("Obj2: count is="+obj2.count);
}
}
Output:
Obj1: count is=2 Obj2: count is=2
2.3.1. Membres static (suite)
Comment ça marche :
-
Les variables
staticsont initialisées au chargement de la classe. -
Les variables
staticd’une classe sont initialisées avant que la moindre instance ne soit créée. -
Les variables
staticsont initialisées avant que la moindre méthodestaticne s’exécute.
2.3.2. Méthodes static
import java.lang.Math;
class Another {
public static void main(String[] args) {
int result;
result = Math.min(10, 20); //calling static method min by writing class name
System.out.println(result);
System.out.println(Math.max(100, 200));
}
}
2.3.3. Méthodes static et appel aux méthodes non-statiques
public class Main {
public static void main(String[] args) {
Main p = new Main();
k();
}
protected Main() {
System.out.print("1234");
}
protected void k() {
}
}
À l’exécution :
Main p = new Main(); // => prints 1234
k() // => raises error
Static method cannot call non-static methods
Bien sûr que si, sauf qu’il faut que cette dernière porte sur une instance de la classe.
Constructors are kind of a method with no return type.
En fait il vaudrait mieux les considérer comme une sorte de méthode statique. En effet elle ne requièrent pas de porter sur un objet!
2.4. Utilité générale des enum
2.4.1. Modélisation
Le type enumération est souvent utilisé en modélisation :
2.4.2. Propriétés
public enum Civilite {
MADAME, MONSIEUR
}
-
Chaque élément d’une énumération est un objet à part entière
-
Les objets
enumhéritent dejava.lang.Enum -
On peut compléter les comportements des objets en ajoutant des méthodes
2.4.3. Méthodes de base
-
toString()System.out.println(Civilite.MADAME); //MADAME -
valueOf()Civilite civilite = Civilite.valueOf("MONSIEUR") ; -
values()Civilite[] civilites = Civilite.values() ; -
ordinal()Civilite civilite = Civilite.MONSIEUR ; System.out.println("Civilite : " + civilite + " [" + civilite.ordinal() + "]") ; // Civilite : MONSIEUR [1]Le 1er numéro d’ordre est 0. -
compareTo()System.out.println(Civilite.MADAME.compareTo(Civilite.MONSIEUR)) ; // -1
2.4.4. Exemple plus complexe
public enum Langage {
//Objets directement construits
JAVA("Langage JAVA", "Eclipse"),
C ("Lanage C", "Code Block"),
CPlus ("Langage C++", "Visual studio"),
PHP ("Langage PHP", "PS Pad");
private String name = "";
private String editor = "";
//Constructeur
Langage(String name, String editor){
this.name = name;
this.editor = editor;
}
public void getEditor(){
System.out.println("Editeur : " + editor);
}
public String toString(){
return name;
}
public static void main(String args[]){
Langage l1 = Langage.JAVA;
Langage l2 = Langage.PHP;
l1.getEditor();
l2.getEditor();
}
}
3. Construire ses applications
Pour générer un programme, une documentation, à partir des sources, on peut :
-
Soit utiliser un environnement intégré comme eclipse
-
Soit construire les sorties (on parle de Build) à partir des sources
Nous nous intéressons dans cette section à cette deuxième catégorie.
Il existe plusieurs outils :
3.1. Les scripts
#!/bin/sh
UML='model.uml'
TYPE='PNG'
DOCLETPATH='/Users/bruel/dev/teaching/dut/cpoa/doclet'
PUMLPATH='/Users/bruel/dev/teaching/dut/cpoa/util'
echo "Creating $UML..."
javadoc \
-private \
-quiet \
-J-DdestinationFile=$UML \
-J-DcreatePackages=false \
-J-DshowPublicMethods=true \
-J-DshowPublicConstructors=false \
-J-DshowPublicFields=true \
-doclet de.mallox.doclet.PlantUMLDoclet -docletpath $DOCLETPATH/plantUmlDoclet.jar \
-sourcepath src src/**
echo "Done."
TYPE='png'
echo "Converting $UML to $TYPE..."
java -jar $PUMLPATH/plantuml.jar \
-config $PUMLPATH/config.cfg \
-t $TYPE $UML
echo "Done."
set UML=TD1.uml
set TYPE='PNG'
set DOCLETPATH=E:\IUT-S3\CPOA\TP1\SuperCanardBof
echo "Creating %UML%..."
rem javadoc -private -quiet -J-DdestinationFile=%UML% -J-DcreatePackages=false -J-DshowPublicMethods=true -J-DshowPublicConstructors=false -J-DshowPublicFields=true -doclet de.mallox.doclet.PlantUMLDoclet -docletpath %DOCLETPATH%\plantUmlDoclet.jar src\canard\*.java
echo "Done."
javadoc -J-DdestinationFile=%UML% -J-DcreatePackages=false -J-DshowPublicMethods=true -J-DshowPublicConstructors=false -J-DshowPublicFields=true -doclet de.mallox.doclet.PlantUMLDoclet -docletpath plantUmlDoclet.jar src\appli\*.java src\armes\*.java src\armes\impl\*.java
set TYPE='png'
echo "Converting %UML% to $TYPE..."
java -jar %DOCLETPATH%\plantuml.jar -config "%DOCLETPATH%\config.cfg" -t %TYPE% %UML%
echo "Done."
- Avantages
-
-
Faciles
-
Rapides
-
Beaucoup d’exemples
-
- Inconvénients
-
-
Pas portables sur d’autres systèmes (no comment ;-)
-
Peu lisibles
-
Peu évolutifs
-
3.2. make
1 #-----------------------------------------------------
2 ICONSDIR=images/icons
3 IMAGESDIR=images
4 STYLE=/Users/bruel/Dropbox/Public/dev/asciidoc/stylesheets/golo-jmb.css
5 DOCTOR=asciidoctor -a icons -a iconsdir=$(ICONSDIR) -a images=$(IMAGESDIR) -a source-highlighter=$(HIGHLIGHT)
6 DECK=swiss
7 EXT=asc
8 PANDOC=pandoc
9 OUTPUT=.
10 DEP=definitions.txt glossaire.txt refs.txt
11 #-----------------------------------------------------
12
13 all: $(OUTPUT)/*.html
14
15 images/%.png: images/%.plantuml
16 @echo '==> Compiling plantUML files to generate PNG'
17 java -jar plantuml.jar $<
18
19 %.html: %.$(EXT) $(DEP)
20 @echo '==> Compiling asciidoc files with Asciidoctor to generate HTML'
21 $(DOCTOR) -a toc2 -b html5 -a numbered -a eleve $<
22
23 %.deckjs.html: %.$(EXT) $(DEP)
24 @echo '==> Compiling asciidoc files to generate Deckjs'
25 $(DOCTOR) -T /Users/bruel/dev/asciidoctor-backends/haml/deckjs/ -a slides \
26 -a data-uri -a deckjs_theme=$(DECK) \
27 -a icons -a iconsdir=$(ICONSDIR) \
28 -a images=$(IMAGESDIR) -a prof -o $@ $<
29
30 %-sujet.html: %.$(EXT) $(DEP)
31 @echo '==> Compiling asciidoc files with Asciidoctor to generate HTML'
32 $(DOCTOR) -a compact -a theme=compact -b html5 -a numbered -a eleve \
33 -a data-uri $< -o $@
34
35 %-prof.html: %.$(EXT) $(DEP)
36 @echo '==> Compiling asciidoc files with Asciidoctor to generate HTML'
37 $(DOCTOR) -a prof -a correction -a theme=compact -b html5 -a numbered \
38 -a data-uri $< -o $@
%.html: %.$(EXT) $(DEP)
@echo '==> Compiling asciidoc files with Asciidoctor to generate HTML'
$(DOCTOR) -a toc2 -b html5 -a numbered -a eleve $<
Exemple d’utilisation :
$ make wip.html ==> Compiling asciidoc files with Asciidoctor to generate HTML asciidoctor -a icons -a iconsdir=images/icons -a images=images -a source-highlighter=pygments -a toc2 -b html5 -a numbered -a eleve wip.asc ... $ make wip.html make: 'wip.html' is up to date.
3.3. ant
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project default="main" name="EssaiBuild">
<target name="main">
<echo message="Version d'Ant utilisée: ${ant.version}"/>
<javadoc doclet="de.mallox.doclet.PlantUMLDoclet"
docletpath="plantUmlDoclet.jar"
access="private"
additionalparam=
"-encoding utf-8 -J-DdestinationFile=uml.txt -J-DcreatePackages=false -J-DshowPublicMethods=true -J-DshowPublicConstructors=false -J-DshowPublicFields=true"
>
<packageset dir="../src">
<include name="**"/>
</packageset>
</javadoc>
<java jar="plantuml.jar" fork="true" maxmemory="128m">
<arg value="uml.txt"/>
</java>
</target>
</project>
Exemple d’utilisation :
$ ant main Buildfile: build.xml main: [javadoc] Generating Javadoc [javadoc] Javadoc execution [javadoc] Loading source files for package pizzeriafactorysample... [javadoc] Constructing Javadoc information... [javadoc] PlantUMLDoclet.createPlantUml() - start [javadoc] open outputfile: uml.txt [javadoc] write interfaces/ abstract classes... [javadoc] write content... ... [javadoc] skip association for Pizza --> java.lang.String [javadoc] skip association for Pizza --> java.lang.String [javadoc] skip association for Pizza --> java.lang.String [javadoc] skip association for Pizza --> java.util.ArrayList [javadoc] PlantUMLDoclet.createPlantUml() - end BUILD SUCCESSFUL Total time: 9 seconds
Exemple d’tilisation dans eclipse pour la génération de fichier
du type TD.uml :
-
Créer un répertoire tools et mettre dedans :
-
Plantuml.jar -
Plantumldoclet.jar -
build.xml
-
-
Faire un Click_Droit sur
build.xmlet chosirBien choisir celui avec les points de suspensions.
-
Dans l’onglet
Environment:-
Créer une nouvelle variable de nom
Pathet de valeur : le répertoire de la JDK (où se trouvejavadocoujavadoc.exe)
-
-
Exécuter Run
Faire Refresh dans le navigateur pour voir les 2 fichiers générés ( uml.txtetuml.png). -
Il suffit de faire un Click_Droit sur
build.xmlet chosir (SANS les 3 points de suspensions) pour relancer la génération du diagramme.
3.4. Maven
Convention over Configuration
Exemples de conventions :
-
le code source est supposé se trouver dans
${basedir}/src/main/java -
les différentes ressources dans
${basedir}/src/main/resources -
les tests dans
${basedir}/src/test -
un projet est supposé produire un fichier
JAR -
Maven suppose que vous voulez compiler en bytecode dans
${basedir}/target/classes -
et ensuite créer votre fichier
JARdistribuable dans${basedir}/target
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>my-project</artifactId>
<version>1.0</version>
</project>
La commande :
$ mvn install
-
va traiter les ressources,
-
compiler le source,
-
exécuter les tests unitaires,
-
créer un JAR, et
-
installer ce JAR dans le dépôt local.
La commande :
$ mvn site
va créer un fichier index.html dans target/site contenant des liens vers
la JavaDoc et quelques rapports sur votre code source.
|
Pour comparer, voici l’équivalent ant :
|
3.5. Ivy
<ivy-module version="2.0">
<info organisation="org.apache" module="hello-ivy"/>
<dependencies>
<dependency org="commons-lang" name="commons-lang" rev="2.0"/>
<dependency org="commons-cli" name="commons-cli" rev="1.0"/>
</dependencies>
</ivy-module>
<project xmlns:ivy="antlib:org.apache.ivy.ant" name="hello-ivy" default="run">
...
<!-- =================================
target: resolve
================================= -->
<target name="resolve" description="--> retrieve dependencies with ivy">
<ivy:retrieve />
</target>
</project>
3.6. Graddle
Gradle combine la flexibilité de ant avec les conventions de Maven mais
évite les inconvénients de XML.
task hello {
doLast {
println 'Hello world!'
}
}
$ gradle hello :hello Hello world! BUILD SUCCESSFUL Total time: 3.486 secs
4. Design patterns
4.1. Introduction : importance des patrons
Science is what we understand well enough to explain to a computer. Art is everything else we do.
4.1.1. Le patron Strategy
Principes de conception
|
Principe de conception
|
|
Principe de conception
|
|
Principe de conception
|
Définition du patron
|
Design pattern : Stratégie (Strategy)
Stratégie définit une famille d’algorithmes, encapsule chacun d’eux et les rend interchangeables. Il permet à l’algorithme de varier indépendamment des clients qui l’utilisent. Figure 3. Modèle UML du patron Strategy
|
Premier exemple d’utilisation
Pourquoi n’a-t’on pas utilisé Strategy pour afficher() ou nager()?
|
Autre exemple concret
| L’exemple qui suit est tiré de ce cours. |
Le problème
Vous avez une classe FileWriter qui a pour rôle d’écrire dans un fichier
ainsi qu’une classe DBWriter. Dans un premier temps, ces classes ne
contiennent qu’une méthode write() qui n’écrira que le texte passé
en paramètre.
Au fil du temps, vous vous rendez compte que c’est dommage qu’elles ne fassent que ça et vous aimeriez bien qu’elles puissent écrire en différents formats (HTML, XML, etc.) : les classes doivent donc formater puis écrire.
La solution
|
L’interface en
PHP (code source ici)
|
|
La classe abstraite
Writer (code source ici)
|
|
La classe
FileWriter (code source ici)
|
|
La classe
DBWriter (code source ici)
|
Enfin, nous avons nos trois formateurs.
L’un ne fait rien de particulier (TextFormater),
et les deux autres formatent le texte en deux langages
différents (HTMLFormater et XMLFormater).
|
La classe
TextFormater (code source ici)
La classe
HTMLFormater (code source ici)
La classe
XMLFormater (code source ici)
|
D’autres exemples
-
La fonction standard
sort()de python>>> sorted("This is a test string from Andrew".split(), key=str.lower) ['a', 'Andrew', 'from', 'is', 'string', 'test', 'This'] -
Stratégie de cryptage en fonction de la taille d’un fichier
File file = getFile(); Cipher c = CipherFactory.getCipher( file.size() ); c.performAction(); // implementations: interface Cipher { public void performAction(); } class InMemoryCipherStrategy implements Cipher { public void performAction() { // load in byte[] .... } } class SwaptToDiskCipher implements Cipher { public void performAction() { // swapt partial results to file. } }
| Plus de détails ici |
4.1.2. (non) Réutilisation
| Les patrons ne sont pas réutilisables! |
Il faut implémeter la solution qu’il représente à chaque fois.
Exception : certains font l’objet d’une librairie.
Par exemple le patron Singleton existe dans la bibliothèque standard du langage en Ruby. C’est un mixin qu’il suffit d’inclure dans la classe qui doit être un singleton.
class Klass
include Singleton
# ...
end
a,b = Klass.instance, Klass.instance
a == b
# => true
Klass.new
# => NoMethodError - new is private ...
4.1.3. Association ou composition
On trouve deux modèles UML™ :
Et donc deux implémentations :
public class Colvert extends Canard {
protected Colvert() {
this(new VolerAvecDesAiles(), new Cancan());
}
...
c1 = new Colvert();
...
vol = new VolerAvecDesAiles();
cri = new Cancan();
c1 = new Colvert(vol,cri);
...
4.2. Un peu d’histoire
- 1977
- 1987
-
Beck et Cunningham : patterns pour des interfaces utilisateurs
- 1988
-
Meyer : livre sur l’orienté objet (langage Eiffel), devenu la bible pour beaucoup de programmeurs (cf. [Meyer88])
- 1990-1995
-
Gamma, Helm, Johnson et Vlissides : LE livre de référence (cf. [GoF])
- 2003
-
Martin : principes SOLID (cf. [Martin03])
- 2004
-
Craig Larman décrit des modèles de conception : les Patterns GRASP (cf. [Larman05])
| Les patterns de ce livre sont connus comme les Gof pour « Gang of Four ». |
4.3. Exemples de bons principes
SOLID:
-
Single Responsibility Principle
-
Open-Closed Principle
-
Liskov Substitution Principle
-
Interface Segregation Principle
-
Dependency Inversion Principle
4.3.2. Open-Closed Principle
Ouvert à l'extension mais fermé à la modification
| Une fois écrite et testée, une classe ne devrait être modifiée que pour être corrigée! Toute modification devrait être possible par extension. |
4.3.3. Liskov Substitution Principle
Une classe doit pouvoir être remplacée par une instance d'un de ses sous-types, sans modifier la cohérence du programme
Un carré est un rectangle a deux côtés égaux.
Peut-on toujours substituer un Carré à la place d’un Rectangle ?
|
4.3.4. Interface Segregation Principle
Préférer plusieurs interfaces spécifiques pour chaque client plutôt qu'une seule interface générale
4.3.5. Dependency Inversion Principle
Il faut dépendre des abstractions, pas des implémentations
Ce principe indique :
-
Les modules de haut niveau (abstraits) ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d’abstractions.
-
Les abstractions ne doivent pas dépendre des détails d’implémentation. C’est l’inverse : les détails doivent dépendre des abstractions.
| Ainsi ce principe va à l’encontre de l’intuition classique. |
4.3.6. SOLID et patrons
|
QUESTION
Lesquels des 5 principes SOLID s’appliquent bien à Strategy ? |
4.4. GRASP
The critical design tool for software development is a mind well educated in design principles. It is not the UML or any other technology.
2005
Il s’agit d’un ensemble de patrons, plutôt orientés conception (UML). Nous en aborderons certains au travers des exemples de ce module (cf. [Larman05]).
4.5. Les patrons : comment ça marche ?
4.5.1. Intérêt
-
Réponses éprouvées à des problèmes récurrents
-
Vocabulaire commun
T’as qu’à utiliser une factory!
4.5.2. Définifion
-
Nom
-
Problème
-
Solution
-
Conséquences
Exemple de Strategy :
- Nom
-
Strategy
- Problème
-
Situations où il est nécessaire de pouvoir définir dynamiquement les algorithmes utilisés.
- Solution
-
Définir une famille d’algorithmes, encapsuler chacun d’eux en tant qu’objet, et les rendre interchangeables.
- Conséquences
-
Ce patron laisse les algorithmes changer indépendamment des clients qui les emploient.
4.5.3. Patrons à aborder
-
Ceux déjà pratiqués
-
[Fabrique] (factory) (cf. parser sax)
-
Les "pressentis"
-
Les nouveaux
-
Décorateur
-
Commande
-
Façade
-
Patron de méthode
-
-
Les "avancés"
-
Chaînes de responsabilité
-
Visiteur
-
-
Ceux qu’on n’aura pas le temps d’aborder
-
Prototype
-
Mémento
-
Médiateur
-
Interprète
-
Poids-mouche
-
Monteur
-
Pont
-
-
Concepts avancés
-
Patrons de patrons (exemple du MVC)
-
Anti-patrons
-
5. Le patron Fabrique
5.1. Principes de conception
|
Design pattern : Fabrique (Factory)
Fabrique (simple) définit une interface pour la création d’un objet, mais en laissant à des sous-classes le choix des classes à instancier (voir aussi Fabrique abstraite). Figure 9. Modèle UML du patron Fabrique
|
5.3. Autre exemple concret
<?php
class DBFactory
{
public static function load($sgbdr)
{
$classe = 'SGBDR_' . $sgbdr;
if (file_exists($chemin = $classe . '.class.php'))
{
require $chemin;
return new $classe;
}
else
{
throw new RuntimeException('La classe <strong>' . $classe . '</strong> n\'a pu être trouvée !');
}
}
}
?>
<?php
try
{
$mysql = DBFactory::load('MySQL');
}
catch (RuntimeException $e)
{
echo $e->getMessage();
}
?>
public class XmlExprParser {
public static Expression fromFile(String file) throws ... {
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setValidating(true);
SAXParser sp = spf.newSAXParser();
ExprHandler ep = new ExprHandler();
sp.parse(file, ep);
return ep.getResult();
}
}
5.4. Mais c’est pas fini!
Reprenons nos pizzas vues en TD
-
Aucune variable ne doit contenir une référence à une classe concrète.
-
Aucune classe ne doit dériver d’une classe concrète.
-
Aucune classe ne doit redéfinir une méthode implémentée dans une classe de base.
public interface FabriqueIngredientsPizza {
public Pate creerPate ();
public Sauce creerSauce();
public Fromage creerFromage();
public Legumes[] creerLegumes();
public Poivrons creerPoivrons();
public Moules creerMoules();
}
public class FabriqueIngredientsPizzaBrest implements FabriqueIngredientsPizza {
public Pate creerPate() {
return new PateFine();
}
public Sauce creerSauce() {
return new SauceMarinara();
}
...
}
public class FabriqueIngredientsPizzaStrasbourg implements FabriqueIngredientsPizza {
public Pate creerPate() {
return new PateEpaisse();
}
public Sauce creerSauce() {
return new SauceTomateCerise();
}
...
}
public class PizzaFromage extends Pizza {
FabriqueIngredientsPizza fabriqueIngredients;
public PizzaFromage(FabriqueIngredientsPizza fabriqueIngredients) {
this.fabriqueIngredients = fabriqueIngredients;
}
void preparer() {
System.out.println("Préparation de " + nom);
pate = fabriqueIngredients.creerPate();
sauce = fabriqueIngredients.creerSauce();
fromage = fabriqueIngredients.creerFromage();
}
}
5.5. Fabrique abstraite
Nous sommes arrivé à une version du patron Fabrique appelée Fabrique Abstraite :
Fabrique (abstraite) fournit une interface pour la création de familles d’objets apparentés ou interdépendants, sans qu’il soit nécessaire de spécifier leurs classes concrètes (voir aussi Fabrique).
6. Un nouveau diagramme UML très utile
Les diagrammes d'états-transitions (plus simplement diagramme d’état) d’UML™ décrivent le comportement interne d’un objet à l’aide d’un automate à états finis.
Les notions importantes de ce diagramme :
-
états
-
actions
-
événements déclencheurs
-
signaux
-
invocations de méthode
-
6.1. Transitions
- Événement
-
Un signal, une invocation de méthode, etc.
- Condition
-
Un booléen
- Action
-
Affectation, invocation de méthode
6.2. Exemple de transitions
6.3. Refactoring
On peut remplacer les actions systématiques des transitions entrantes :
par une transition interne : entry :
6.4. Transitions internes
- entry
-
permet de spécifier une activité qui s’accomplit quand on entre dans l’état.
- exit
-
permet de spécifier une activité qui s’accomplit quand on sort de l’état.
- do
-
commence dès que l’activité
entryest terminée. Lorsque cette activité est terminée, une transition d’achèvement peut être déclenchée. Si une transition se déclenche pendant que l’activitédoest en cours, cette dernière est interrompue et l’activitéexitde l’état s’exécute.
6.5. Conditions
|
QUESTION
|
6.6. Etats complexes
Un état peut lui-même être doté d’un comportement et donc représenter à lui seul une machine à état. Par exemple :
|
QUESTION
Intégrez les semestres aux diagramme précédent (étudiants) |
6.7. Notion de concurrence
On peut représenter l’évolution de différentes machines de manière concurrente (parallèle). Par exemple :
|
QUESTION
Réalisez le diagramme d’état d’une machine à boisson rendant la monnaie. |
| Pour aller plus loin sur ce diagramme : http://laurent-audibert.developpez.com/Cours-UML/?page=diagramme-etats-transitions |
7. Le patron Etat
Soit la machine à état suivante :
7.1. Implémentation intuitive
public void insererPiece() {
if (etat == A_PIECE) {
System.out.println("Vous ne pouvez plus insérer de pièces");
} else if (etat == EPUISE) {
System.out.println("Vous ne pouvez pas insérer de pièce, nous sommes en rupture de stock");
} else if (etat == VENDU) {
System.out.println("Veuillez patienter, le bonbon va tomber");
} else if (etat == SANS_PIECE) {
etat = A_PIECE;
System.out.println("Vous avez inséré une pièce");
}
}
7.2. Erreur d’implémentations
-
Ce code n’adhère pas au principe Ouvert-Fermé.
-
Cette conception n’est pas orientée objet.
-
Les transitions ne sont pas explicites. Elles sont enfouies au milieu d’un tas d’instructions conditionnelles.
-
Nous n’avons pas encapsulé ce qui varie.
-
Les ajouts ultérieurs sont susceptibles de provoquer des bugs dans le code.
7.3. Une meilleure implémentation
-
Définir une nouvelle interface
Etatqui contiendra une méthode pour chaque action -
Implémenter une classe pour chaque
Etat. Elles seront responsable du comportement. -
Se débarrasser de toutes les instructions conditionnelles et les remplacer par une délégation à la classe adéquate.
7.4. Illutration
Etape 1 :
Etape 2
public class EtatSansPiece implements Etat {
// Va falloir remplir ici...
public void insererPiece() {
System.out.println("Vous avez inséré une pièce");
// changer d'état si besoin
}
...
}
Etape 3
public class Distributeur {
Etat etat = new EtatSansPiece(); // état initial
...
public void insererPiece() {
etat.insererPiece(); // on délègue à l'état le soin de réagir
}
...
}
Etape 4 (enfin, retour sur l’étape 2) : une solution possible…
public class EtatSansPiece implements Etat {
Distributeur distributeur; // référence au distributeur qu'on gère
public EtatSansPiece(Distributeur distributeur) {
this.distributeur = distributeur;
}
public void insererPiece() {
System.out.println("Vous avez inséré une pièce");
distributeur.setEtat(distributeur.getEtatAPiece());
}
...
}
7.5. Le patron Etat
Etat permet à un objet de modifier son comportement, quand son état interne change. Tout se passe comme si l’objet changeait de classe.
|
QUESTION
Que pensez-vous de notre solution précédente par rapport à ce diagramme UML? |
L’état possède une référence vers le contexte (Distributeur dans notre exemple).
Glossaire et définition
| Ces définitions seront enrichies au fur et à mesure des patrons étudiés. |
Patrons de création
- Fabrique
-
Fabrique (simple) définit une interface pour la création d’un objet, mais en laissant à des sous-classes le choix des classes à instancier (voir aussi Fabrique abstraite).
- Fabrique abstraite
-
Fabrique (abstraite) fournit une interface pour la création de familles d’objets apparentés ou interdépendants, sans qu’il soit nécessaire de spécifier leurs classes concrètes (voir aussi Fabrique).
Patrons comportementaux
Patrons structurels
Références
-
[GoF] Design Patterns: Elements of reusable object oriented software. 1994.
-
[Freeman04] Design Pattren - Head First. Bert Bates, Eric Freeman, Elisabeth Freeman, Kathy Sierra. O’Reilly, 09/2004.
-
[Freeman05] Tête la première : Design Pattern. Bert Bates, Eric Freeman, Elisabeth Freeman, Kathy Sierra. Editions O’Reilly. 2005.
-
[Cysboy] Apprenez à programmer en Java. Par
cysboy. Disponible ici (le 2015-12-04). -
GOPROD - De bonnes pratiques au service de la conception orientée objets. Disponible ici (le 2015-12-04).
-
[Larman05] Larman, Craig. Applying UML and Patterns – An Introduction to Object-Oriented Analysis and Design and Iterative Development (3rd ed.). Prentice Hall. 2005. ISBN 0-13-148906-2.
-
[Meyer88] Meyer, Bertrand. Object-Oriented Software Construction. Prentice Hall. 1988. ISBN 0-13-629049-3.
-
[Martin03] “Principles Of OOD”, Robert C. Martin (“Uncle BOB”), http://butunclebob.com (Last verified 2014-07-17). Note the reference to “the first five principles”, though the acronym is not used in this article. Dates back to at least 2003.